﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace PhoobearLibs.Resource.Resx
{
    /// <summary>
    /// 리소스를 표현하는 추상 클래스
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    public abstract class Resource<TValue> : IEquatable<Resource<TValue>>
    {

#region Public Event

        /// <summary>
        /// 레코드 추가됨 이벤트
        /// </summary>
        public event Action<object, AddResourceOperationEventArgs<TValue>> RecordAdded;

        /// <summary>
        /// 레코드 삭제됨 이벤트
        /// </summary>
        public event Action<object, RemoveResourceOperationEventArgs<TValue>> RecordRemoved;

        /// <summary>
        /// 레코드 업데이트 됨 이벤트
        /// </summary>
        public event Action<object, UpdateResourceOperationEventArgs<TValue>> RecordUpdated;

        /// <summary>
        /// 에러 발생됨 이벤트
        /// </summary>
        public event Action<object, ErrorResourceOperationEvenrArgs<TValue>> ErrorOccured;

#endregion

#region Public Property

        /// <summary>
        /// 내부 리소스 데이터 
        /// </summary>
        private Dictionary<string, TValue> _data = null;
        public virtual Dictionary<string, TValue> Data
        {
            get
            {               
                return _data;
            }

            protected set
            {
                _data = value;
            }
        }

#endregion

#region Protected Methods

        /// <summary>
        /// 에러 핸들러 여기서 로거와 연결합니다.
        /// </summary>
        /// <param name="exc">에러</param>
        protected virtual void OnError(Exception exc)
        {
#if DEBUG
            System.Diagnostics.Debug.WriteLine(exc.ToString());
#endif

            if (this.ErrorOccured != null)
            {
                this.ErrorOccured(this, new ErrorResourceOperationEvenrArgs<TValue>() { Exception = exc });
            }
        }

        /// <summary>
        /// 리소스 추가
        /// </summary>
        /// <param name="resource">새 리소스 추가</param>
        protected virtual void OnResourceAdded(KeyValuePair<string, TValue> resource)
        {
            if (this.RecordAdded != null)
            {
                this.RecordAdded(this, new AddResourceOperationEventArgs<TValue>() { Resource = this, Key = resource.Key, Value = resource.Value });
            }
        }

        /// <summary>
        /// 리소스 삭제
        /// </summary>
        /// <param name="resource">삭제된 리소스</param>
        protected virtual void OnResourceRemoved(KeyValuePair<string, TValue> resource)
        {
            if (this.RecordRemoved != null)
            {
                this.RecordRemoved(this, new RemoveResourceOperationEventArgs<TValue>() { Resource = this, Key = resource.Key, Value = resource.Value });
            }
        }

        /// <summary>
        /// 리소스 업데이트
        /// </summary>
        /// <param name="oldValue">구형값</param>
        /// <param name="newValue">신형값</param>
        protected virtual void OnResourceUpdated(KeyValuePair<string, TValue> oldValue, KeyValuePair<string, TValue> newValue)
        {
            if (this.RecordUpdated != null)
            {
                this.RecordUpdated(this, new UpdateResourceOperationEventArgs<TValue>() { Resource = this, Key = oldValue.Key, OldValue = oldValue.Value, NewValue = newValue.Value });
            }
        }

#endregion

#region Public Methods 
        
        /// <summary>
        /// 이 객체의 문자열 형태 표현을 반환합니다.
        /// </summary>
        /// <returns>이 리소스 객체의 문자열 정의</returns>
        public override string ToString()
        {
            return base.ToString();                            
        }


        #region IEquatable<Resource<TValue>> 멤버

        /// <summary>
        /// 두 객체가 동일한지 검사
        /// </summary>
        /// <param name="other">비교할 객체</param>
        /// <returns>동일하면 true 틀리면 false</returns>
        public virtual bool Equals(Resource<TValue> other)
        {
            var result = (from l in this.Data
                          join r in other.Data on new { l.Key, l.Value } equals new { r.Key, r.Value } into m
                          from x in m.DefaultIfEmpty(new KeyValuePair<string, TValue>(string.Empty, default(TValue)))
                          select x.Key).Distinct();

            bool isDataEquals = !result.Any(p => { return p.Equals(string.Empty); });

            return isDataEquals;
        }

        #endregion

        /// <summary>
        /// 해당 아이디의 데이터를 뺍니다.
        /// </summary>
        /// <param name="id">아이디</param>
        /// <returns>리소스가 빠진 레코드</returns>
        public Resource<TValue> Subtract(string id)
        {
            this.Data.Remove(id);
            return this;
        }

        /// <summary>
        /// 해당 아이디들의 데이터를 파일에서 뺍니다.
        /// </summary>
        /// <param name="ids">아이디 모음</param>
        /// <returns>해당 아이디들의 레코드가 빠진 파일</returns>
        public Resource<TValue> Subtract(ICollection<string> ids)
        {
            foreach (var item in ids)
            {
                this.Subtract(item);
            }

            return this;
        }

        /// <summary>
        /// 다른 파일에 있는 중복된 아이디의 레코드를 모두 제거합니다.
        /// </summary>
        /// <param name="file">뺄 레코드가 들어있는 파일</param>
        /// <returns>리소스가 빠진 레코드</returns>
        public Resource<TValue> Subtract(Resource<TValue> file)
        {
            var result = (from l in this.Data
                          join r in file.Data
                          on l.Key equals r.Key into m
                          select l.Key).Distinct();

            for (int index = 0; index < result.Count(); index++)
            {
                string item = result.ElementAt(index);
                this.Subtract(item);
            }

            return this;
        }

        /// <summary>
        /// 새 리소스 파일을 붙입니다.
        /// 중복되는 키의 내용은 무시합니다.
        /// </summary>
        /// <param name="file">리소스 파일</param>
        /// <returns>새 리소스 파일이 결합된 파일</returns>
        public Resource<TValue> Concat(Resource<TValue> file)
        {
            foreach (var item in file.Data)
            {
                this.Concat(item);
            }

            return this;
        }

        /// <summary>
        /// 새 리소스를 파일에 붙입니다.
        /// 중복되는 키의 내용은 무시합니다.
        /// </summary>
        /// <param name="record">리소스</param>
        /// <returns>새 리소스가 추가된 파일</returns>
        public Resource<TValue> Concat(KeyValuePair<string, TValue> record)
        {
            TValue value = default(TValue);

            bool result = this.Data.TryGetValue(record.Key, out value);
            if (result == false)
            {
                this.Data.Add(record.Key, record.Value);
                this.OnResourceAdded(record);
            }

            return this;
        }

        /// <summary>
        /// 두 리소스를 합칩니다.
        /// 중복되는 키가 있을경우 덮어씁니다.
        /// </summary>
        /// <param name="file">파일</param>
        /// <returns>새 리소스가 추가된 파일</returns>
        public Resource<TValue> Combine(Resource<TValue> file)
        {
            foreach (var item in file.Data)
            {
                this.Combine(item);
            }

            return this;
        }

        /// <summary>
        /// 두 리소스를 합칩니다.
        /// 중복되는 키가 있을경우 덮어씁니다.
        /// </summary>
        /// <param name="record">레코드</param>
        /// <returns>새 리소스가 추가된 파일</returns>
        public Resource<TValue> Combine(KeyValuePair<string, TValue> record)
        {
            TValue value = default(TValue);

            bool result = this.Data.TryGetValue(record.Key, out value);
            if (result == false)
            {
                this.Data.Add(record.Key, record.Value);
                this.OnResourceAdded(record);
            }
            else
            {
                TValue oldValue = this.Data[record.Key];
                this.Data[record.Key] = record.Value;
                this.OnResourceUpdated(new KeyValuePair<string, TValue>(record.Key, oldValue), new KeyValuePair<string, TValue>(record.Key, record.Value));
            }

            return this;
        }

#endregion


#region Operators

        /// <summary>
        /// 두 리소스 파일을 붙입니다.
        /// </summary>
        /// <param name="lhs">원본 파일</param>
        /// <param name="rhs">붙일 파일</param>
        /// <returns>새 파일이 붙은 파일</returns>
        public static Resource<TValue> operator +(Resource<TValue> lhs, Resource<TValue> rhs)
        {
            return lhs.Concat(rhs);
        }

        /// <summary>
        /// 리소스 파일에 새 리소스를 붙인다.
        /// </summary>
        /// <param name="lhs">원본 파일</param>
        /// <param name="rhs">붙일 리소스</param>
        /// <returns>새 파일이 붙은 파일</returns>
        public static Resource<TValue> operator +(Resource<TValue> lhs, KeyValuePair<string, TValue> rhs)
        {
            return lhs.Concat(rhs);
        }

        /// <summary>
        /// 좌측 리소스 파일에서 우측 리소스 파일의 내용을 뺍니다.
        /// </summary>
        /// <param name="lhs">원본 파일</param>
        /// <param name="rhs">붙일 파일</param>
        /// <returns>새 파일이 붙은 파일</returns>
        public static Resource<TValue> operator -(Resource<TValue> lhs, Resource<TValue> rhs)
        {
            return lhs.Subtract(rhs);
        }

        /// <summary>
        /// 좌측 리소스 파일에서 우측 리소스 ID를 뺍니다.
        /// </summary>
        /// <param name="lhs">원본 파일</param>
        /// <param name="rhs">뺄 ID</param>
        /// <returns>새 파일이 붙은 파일</returns>
        public static Resource<TValue> operator -(Resource<TValue> lhs, ICollection<string> rhs)
        {
            return lhs.Subtract(rhs);
        }

        /// <summary>
        /// 좌측 리소스 파일에서 우측 리소스 ID를 뺍니다.
        /// </summary>
        /// <param name="lhs">원본 파일</param>
        /// <param name="rhs">뺄 ID</param>
        /// <returns>새 파일이 붙은 파일</returns>
        public static Resource<TValue> operator -(Resource<TValue> lhs, string rhs)
        {
            return lhs.Subtract(rhs);
        }

        /// <summary>
        /// 우측 리소스파일로 좌측 리소스 파일을 업데이트 합니다.
        /// 중복되는 키가 있을경우 그 키의 값을 우측 리소스로 업데이트
        /// </summary>
        /// <param name="lhs">원본파일</param>
        /// <param name="rhs">덮어쓸 파일</param>
        /// <returns>작업결과 정상여부</returns>
        public static Resource<TValue> operator |(Resource<TValue> lhs, Resource<TValue> rhs)
        {
            return lhs.Combine(rhs);
        }

        /// <summary>
        /// 우측 리소스파일로 좌측 리소스 파일을 업데이트 합니다.
        /// 중복되는 키가 있을경우 그 키의 값을 우측 리소스로 업데이트
        /// </summary>
        /// <param name="lhs">원본파일</param>
        /// <param name="rhs">덮어쓸 파일</param>
        /// <returns>작업결과 정상여부</returns>
        public static Resource<TValue> operator |(Resource<TValue> lhs, KeyValuePair<string, TValue> rhs)
        {
            return lhs.Combine(rhs);
        }


#endregion
        
      
    }
}
